package services

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/astaxie/beego/logs"
	"kitalk/models"
	"net/http"
	"reflect"
	"strings"
	"time"

	"github.com/gorilla/websocket"
)

const (
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second

	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10

	// Maximum message size allowed from peer.
	maxMessageSize = 512
)

var (
	newline = []byte{'\n'}
	space   = []byte{' '}
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

// Client is a middleman between the websocket connection and the hub.
type Client struct {
	// The websocket connection.
	conn *websocket.Conn

	// Buffered channel of outbound messages.
	send chan models.WsMessage

	//连接状态 0.连接中 -1.已断开
	status int

	//上面的用户
	User models.User
}

// readPump pumps messages from the websocket connection to the hub.
//
// The application runs readPump in a per-connection goroutine. The application
// ensures that there is at most one reader on a connection by executing all
// reads from this goroutine.
func (c *Client) readPump() {
	defer func() {
		c.conn.Close()
		//清理维持client的用户
		c.status = -1
		UserClientService.clearClients(c.User.Id)
	}()
	c.conn.SetReadLimit(maxMessageSize)
	c.conn.SetReadDeadline(time.Now().Add(pongWait))
	c.conn.SetPongHandler(func(string) error {
		c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil
	})
	for {
		_, messageRaw, err := c.conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				logs.Error("readPump:"+err.Error(), err)
			}
			break
		}
		messageRaw = bytes.TrimSpace(bytes.Replace(messageRaw, newline, space, -1))
		if string(messageRaw) == "ping" {
			continue
		}
		logs.Info("message:%+v", string(messageRaw))

		//解析客户端的json
		wsMessage := models.WsMessage{}
		err = json.Unmarshal(messageRaw, &wsMessage)
		if err != nil {
			logs.Error(err.Error(), err)
			continue
		}

		//发给业务服务器
		err = reflectMqFuncCall(wsMessage, c)
		if err != nil {
			logs.Error(err.Error(), err)
			continue
		}
	}

}

//reflectMqFuncCall 反射动态执行controllers.MqProcessController中的队列处理函数
func reflectMqFuncCall(wsMessage models.WsMessage, client *Client) error {
	//初始化执行对象
	mqProcessController := &MessageService{wsMessage, client}
	action := wsMessage.Action

	//获取执行对象的方法
	v := reflect.ValueOf(mqProcessController)
	vmethod := v.MethodByName("On"+action)
	if vmethod.Kind() != reflect.Func {
		//原函数没有获取到时，重试驼峰方法
		newActionName := getUpperActionName(action)
		if newActionName == action {
			return fmt.Errorf("没有找到可执行函数 action:%s", action)
		}
		//检测驼峰函数是否存在
		vmethod = v.MethodByName("On"+newActionName)
		if vmethod.Kind() != reflect.Func {
			return fmt.Errorf("没有找到可执行函数 action:%s", "On"+newActionName)
		}
	}

	//增加业务异常处理，避免因部分逻辑panic，导致连接中断
	defer func() {
		if err := recover(); err != nil {
			logs.Error("业务异常 panic:", err)
		}
	}()

	//执行函数
	vmethod.Call([]reflect.Value{})

	return nil
}

//getUpperActionName 获取action的驼峰方法
func getUpperActionName(action string) string {
	if action == "" {
		return action
	}
	ss := strings.Split(action, "_")
	if len(ss) == 1 {
		return strings.ToUpper(action[:1])+action[1:]
	}
	for k,v := range ss {
		ss[k] = strings.ToUpper(v[:1])+v[1:]
	}
	return strings.Join(ss, "")
}

// writePump pumps messages from the hub to the websocket connection.
//
// A goroutine running writePump is started for each connection. The
// application ensures that there is at most one writer to a connection by
// executing all writes from this goroutine.
func (c *Client) writePump() {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		c.conn.Close()
		//清理维持client的用户
		c.status = -1
		UserClientService.clearClients(c.User.Id)
	}()
	for {
		select {
		case message, ok := <-c.send:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				// The hub closed the channel.
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				logs.Error("服务端写入管道关闭")
				return
			}

			w, err := c.conn.NextWriter(websocket.TextMessage)
			if err != nil {
				logs.Error(err.Error(), err)
				return
			}

			err = json.NewEncoder(w).Encode(message)
			if err != nil {
				logs.Error(err.Error(), err)
				return
			}

			// Add queued chat messages to the current websocket message.
			n := len(c.send)
			for i := 0; i < n; i++ {
				w.Write(newline)
				err = json.NewEncoder(w).Encode(<-c.send)
				if err != nil {
					logs.Error(err.Error(), err)
					return
				}
			}

			if err := w.Close(); err != nil {
				logs.Error(err.Error(), err)
				return
			}
		case <-ticker.C:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				logs.Error("writePump ticker.C:"+err.Error(), err)
				return
			}
		}
	}
}

func (this *Client) WriteReturn(action string, data map[string]interface{}) {
	var message = models.WsMessage{
		Action:action,
		Data:data,
	}
	this.send <- message
}

// serveWs handles websocket requests from the peer.
func ServeWs(user models.User, w http.ResponseWriter, r *http.Request) {
	//升级http
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		logs.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	client := &Client{conn: conn, User:user, send: make(chan models.WsMessage, 256)}

	// Allow collection of memory referenced by the caller by doing all work in
	// new goroutines.
	go client.writePump()
	go client.readPump()

	//绑定用户
	UserClientService.AddUser(user.Id, client)
}